<!--
 * @Date: 2024-12-19 09:03:34
 * @Author: LiSong
 * @LastEditors: LiSong
 * @LastEditTime: 2025-03-21 09:30:47
 * @Description: 
-->
<template>
  <div class="hp-chart-map-panel">
    <div class="hp-chart-map-panel-header" v-if="schema.showHeader">
      <div class="header-title" :style="{ 'text-align': schema.textAlign }">
        <span>{{ schema.label }}</span>
      </div>
      <div class="header-tools"></div>
    </div>
    <div class="hp-chart-map-panel-drilling">
      <div class="drilling-left" v-show="showBackLevel">
        <span>{{ drillState.stepNames.join(' > ') }}</span>
      </div>
      <div class="drilling-right">
        <el-button link type="primary" @click="goBackPrevLevel" v-show="showBackLevel">
          <span>返回上一级</span>
        </el-button>
      </div>
    </div>
    <div class="hp-chart-map-panel-body">
      <div
        ref="chartRef"
        class="chart-warp"
        v-hpresize="chartsHook.resizeChart"
        :id="schema.id"
      >
      </div>
    </div>
  </div>
</template>

<script setup>
import { isEmpty } from '@hp/utils/data.js';
import { commonProps } from '@hp/panels/cfg.js'
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue';

import * as echarts from 'echarts';
import { useCharts } from '@hp/hooks/useCharts.js'
import { useDbSource } from '@hp/hooks/useDbSource.js'
import { useEventFunc } from '@hp/hooks/useEventFunc.js'

const emits = defineEmits([ 'mounted' ])
const props = defineProps({
  ...commonProps
})

// 图例通用操作
const chartsHook = useCharts({ ...props })
// 数据源相关操作
const dbsourceHook = useDbSource({ ...props })
// 事件函数相关操作
const eventFuncHook = useEventFunc({ ...props })

const { chartMod, chartRef } = chartsHook

// 默认地图编码（全国）
const defMapCode = '100000'
// 图表数据集
const chartState = reactive({
  // 地图名称
  mapCode: props.schema.mapCode || defMapCode,
  // 地图数据
  mapData: [],
  // 数据源响应数据
  resData: [],
  // 当前地图GeoJSON
  mapGeoJSON: {},
  // 加载过地图的GeoJSON
  mapGeoJSONCache: {},
})

// 图表配置
const chartOption = computed(() => {
  const { schema } = props
  const { echarts } = schema
  const { series, visualMap } = loadDataOption()
  let options = {
    geo: {
      ...echarts.geo,
      map: `${chartState.mapCode}`,
      scaleLimit: {
        min: 1,
        max: 4,
      },
    },
    tooltip: echarts.tooltip,
    toolbox: echarts.toolbox,
    series,
    visualMap
  }
  return options
})
/**
 * 初始化图例
 */
const initChart = async () => {
  await nextTick()
  // 初始化图表
  chartMod.value = null
  if (chartMod.value) chartMod.value.dispose()
  chartMod.value = echarts.init(chartRef.value)
  chartMod.value.showLoading('default', { text: '加载中...' });
  // 注册地图JSON
  await registerChartMap(chartState.mapCode)
  // 初始化点击事件
  initChartClick()
  chartMod.value.hideLoading();
}
/**
 * 初始化图例点击事件
 */
const initChartClick = () => {
  chartMod.value.on('click', function (data) {
    eventFuncHook.onClick(data.data, { otherData: { ...chartState, serie: data } })
    drillDownMapChart(data.data)
  })
}
/**
 * 加载数据配置
 */
const loadDataOption = () => {
  const { schema } = props
  const { echarts } = schema
  const resData = chartState.resData
  const mapData = chartState.mapData
  const serieData = mergeMapData(resData, mapData)
  const { visualMap } = echarts
  // 处理视觉映色组件配置
  visualMap['text'] = [visualMap.topText, visualMap.bottomText]
  return {
    visualMap,
    series: [{
      type: 'map',
      name: '地图',
      data: serieData,
      geoIndex: 0,
    }]
  }
}
/**
 * 合并地图数据
 * 
 * @param targetData 目标数据
 * @param sourceData 原有数据
 */
const mergeMapData = (targetData, sourceData) => {
  if (targetData.length == 0) return sourceData
  let mergeKey = 'adcode'
  for (let sourceItem of sourceData) {
    let targetItem = targetData.find(item => item[mergeKey] == sourceItem[mergeKey])
    if (!targetItem) {
      sourceItem['value'] = null
    } else {
      sourceItem['value'] = targetItem.value
    }
  }
  return sourceData
}

// 下钻功能数据集
const drillState = reactive({
  // 是否启用下钻
  enabled: props.schema.drillEnabled || false,
  // 用于记录下钻的地图信息
  mapList: [],
  // 用于记录下钻的地图名称
  stepNames: [],
  // 用于限制下钻的地图层级
  limitLevel: props.schema.drillLevel || 'district',
})
// 是否显示返回上一级按钮
const showBackLevel = computed(() => {
  return drillState.mapList.length > 0
})
/**
 * 更新下钻数据
 */
const updateDrillState = () => {
  drillState.enabled = props.schema.drillEnabled
  drillState.limitLevel = props.schema.drillLevel
}
/**
 * 返回上一级地图
 */
const goBackPrevLevel = async () => {
  if (drillState.mapList.length === 0) return
  drillState.stepNames.pop();
  const prevItem = drillState.mapList.pop();

  chartMod.value.showLoading('default', { text: '加载中...' });
  await reloadData({ adcode: prevItem.adcode })
  await registerChartMap(prevItem.adcode, prevItem.prevDir)
  chartMod.value.hideLoading();
}
/**
 * 下钻地图功能
 * 
 * @param data 地图数据
 */
const drillDownMapChart = async (data) => {
  if (!data) return
  // 下钻功能不开启，返回
  if (!drillState.enabled) return
  // 数据为限制层级，返回
  if (data.level === drillState.limitLevel) return
  // 地图数据无子级，返回
  if (!data.childrenNum) return
  
  // 获取文件目录
  let prevDir = `${data.acroutes[1] || ''}`
  let prevCode = data.acroutes.slice(-1)[0]
  drillState.mapList.push({ adcode: prevCode, prevDir })
  drillState.stepNames.push(data.name)

  chartMod.value.showLoading('default', { text: '加载中...' });
  await reloadData({ adcode: data.adcode })
  await registerChartMap(data.adcode, prevDir)
  chartMod.value.hideLoading();
}

/**
 * 加载数据
 */
const loadData = async (params={}) => {
  // 通过数据源配置获取数据
  const res = await dbsourceHook.loadDataSource(props.schema, params)
  // 设置地图数据
  chartState.resData = isEmpty(res) ? [] : res
  // 设置图表选项
  updateChartOption()
}
/**
 * 重新加载数据
 * 
 * @param params 参数
 */
const reloadData = async (params={}) => {
  await loadData(params)
}

/**
 * 注册地图数据
 * 
 * @param mapCode 地图编码
 */
const registerChartMap = async (mapCode, mapDir='') => {
  mapCode = mapCode || defMapCode
  let geoJSON = null
  if (chartState.mapGeoJSONCache[mapCode]) {
    // 读取缓存，避免重复请求
    geoJSON = chartState.mapGeoJSONCache[mapCode]
  } else {
    // 通过接口获取地图的GeoJSON数据
    geoJSON = await reqGetMapGeoJson(mapCode, mapDir)
    chartState.mapGeoJSONCache[mapCode] = geoJSON
  }
  if (!geoJSON) return
  // 将地图数据注册到echarts实例，使用mapCode作为唯一标识
  echarts.registerMap(`${mapCode}`, { geoJSON })
  // 数据赋值
  chartState.mapCode = mapCode
  // 初始化地图数据集
  chartState.mapData = geoJSON.features.map(item => item.properties).filter(item => item.name)
  // 记录当前加载地图geoJSON
  chartState.mapGeoJSON = geoJSON

  // 更新图表配置，触发图表重新渲染
  updateChartOption()
}
/**
 * 更新图例的选项
 */
const updateChartOption = () => {
  if (!chartMod.value) return
  chartMod.value.setOption(chartOption.value)
}
/**
 * 请求获取地图GeoJSON数据
 * 
 * @param mapCode 地图编码
 * @param mapDir 地图目录
 */
const reqGetMapGeoJson = (mapCode, mapDir) => {
  return new Promise(async (resolve, reject) => {
    mapDir = mapDir || defMapCode
    if (mapCode) {
      try {
        const response = await fetch(`/map/${mapDir}/${mapCode}.json`);
        resolve(await response.json())
      } catch (error) {
        console.error('未找到指定文件数据！', error)
        resolve(null)
      }
    } else {
      resolve(null)
    }
  })
}

onMounted(() => {
  nextTick(() => {
    initChart()
    emits('mounted')
  })
})

watch(
  () => props.schema.dataSource,
  (nv, ov) => {
    loadData()
  },
  { deep: true, immediate: true }
)

watch(
  () => props.schema.mapCode,
  async (nv, ov) => {
    registerChartMap(nv)
  },
  { deep: true, immediate: false }
)

watch(
  [
    () => props.schema.echarts
  ],
  (nv, ov) => {
    setTimeout(() => { updateChartOption() }, 100)
  },
  { deep: true, immediate: false }
)

watch(
  [
    () => props.schema.drillLevel,
    () => props.schema.drillEnabled
  ],
  (nv, ov) => {
    updateDrillState()
  }
)

</script>

<style lang="scss" scoped>
.hp-chart-map-panel {
  height: 100%;
  display: flex;
  flex-direction: column;
  &-header {
    height: 45px;
    display: flex;
    align-items: center;
    padding: 0 8px;
    border-bottom: 1px solid #ececec;
    .header-title {
      flex: 1;
      font-size: 16px;
      font-weight: bold;
    }
    .header-tools {
      margin-left: auto;
    }
  }
  &-drilling {
    display: flex;
    align-items: center;
    height: 22px;
    padding-top: 10px;
    padding-left: 10px;
    padding-right: 10px;
    .drilling-right {
      margin-left: auto;
    }
  }
  &-body {
    flex: 1;
    min-height: 10px;
    padding: 8px;
    .chart-warp {
      width: 100%;
      height: 100%;
    }
    .chart-wrapper {
      width: 100%;
      height: 100%;
    }
  }
}
</style>